﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Concurrent;
using System.Threading;
using System.Runtime.Remoting;
using System.Threading.Tasks;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Diagnostics;
using System.Xml.Linq;

using King.Text;
using King.Reflection;
using King.Extensions;
using King.Text.Xml;

namespace King.Text.Html {

    public class Tag { }

    public class HtmlElementAttribute : Attribute {
        private string m_name;
        private bool m_inline;

        public HtmlElementAttribute(string name, bool inline = false) {
            m_name = name;
            m_inline = inline;
        }
        public string Name {
            get { return m_name; }
        }
        public bool Inline {
            get { return m_inline; }
        }
    }
    public class HtmlAttributeAttribute : Attribute {
        private string m_name;
        public HtmlAttributeAttribute() {
        }
        public HtmlAttributeAttribute(string name) {
            m_name = name;
        }
        public string Name {
            get { return m_name; }
        }
    }
    public class HtmlAttributeGroupAttribute : Attribute {
    }

    public static class HtmlLoader {
        private static Dictionary<Type, HtmlElementInfo> m_elements;
        private static Dictionary<Type, HtmlAttributeGroupInfo> m_attributeGroups;

        static HtmlLoader() {
            var elements =
                from o in Assembly.GetExecutingAssembly().GetTypes()
                let attribute = o.GetAttribute<HtmlElementAttribute>()
                where attribute != null
                select new HtmlElementInfo(o, attribute);
            m_elements = elements.ToDictionary(o => o.Type);

            var attributeGroups =
                from o in Assembly.GetExecutingAssembly().GetTypes()
                let attribute = o.GetAttribute<HtmlAttributeGroupAttribute>()
                where attribute != null
                select new HtmlAttributeGroupInfo(o);
            m_attributeGroups = attributeGroups.ToDictionary(o => o.Type);
        }

        public static IEnumerable<HtmlElementInfo> Elements() {
            return m_elements.Values;
        }
        public static IEnumerable<HtmlAttributeGroupInfo> AttributeGroups() {
            return m_attributeGroups.Values;
        }
        public static HtmlElementInfo GetElement(Type type) {
            return m_elements.GetValueOrDefault(type);
        }
        public static HtmlAttributeGroupInfo GetAttributeGroup(Type type) {
            return m_attributeGroups.GetValueOrDefault(type);
        }
    }
    public sealed class HtmlElementInfo {
        private string m_name;
        private Type m_type;
        private bool m_inline;
        private IEnumerable<HtmlAttributeInfo> m_attributes;

        internal HtmlElementInfo(Type type, HtmlElementAttribute htmlAttribute) {
            m_type = type;
            m_inline = htmlAttribute.Inline;
            m_name = htmlAttribute.Name.ToLower();
            
            var attributes =
                from o in type.Get<PropertyInfo>(declaredOnly: true, isInstance: true)
                let attribute = o.GetAttribute<HtmlAttributeAttribute>()
                where attribute != null
                select new HtmlAttributeInfo(this, o, attribute);
            m_attributes = attributes.ToList();
        }

        public Type Type {
            get { return m_type; }
        }
        public string Name {
            get { return m_name; }
        }
        public bool Inline {
            get { return m_inline; }
        }
        public IEnumerable<HtmlAttributeInfo> Attributes() {
            return m_attributes;
        }

        public override string ToString() {
            return m_name;
        }
    }
    public sealed class HtmlAttributeInfo {
        private string m_name;
        private object m_parent;
        private PropertyInfo m_property;

        internal HtmlAttributeInfo(object parent, PropertyInfo property, HtmlAttributeAttribute attribute) {
            m_parent = parent;
            m_name = property.Name.ToLower();
            m_property = property;
        }

        public HtmlElementInfo Element {
            get { return (HtmlElementInfo)m_parent; }
        }
        public HtmlAttributeGroupInfo AttributeGroup {
            get { return (HtmlAttributeGroupInfo)m_parent; }
        }
        public PropertyInfo Property {
            get { return m_property; }
        }
        public string Name {
            get { return m_name; }
        }
        public object GetValue(object target) {
            return m_property.GetValue(target);
        }

        public override string ToString() {
            return m_name;
        }
    }
    public sealed class HtmlAttributeGroupInfo {
        private Type m_type;
        private IEnumerable<HtmlAttributeInfo> m_attributes;

        internal HtmlAttributeGroupInfo(Type type) {
            m_type = type;
            
            var attributes =
                from o in type.Get<PropertyInfo>(declaredOnly: true, isInstance: true)
                let attribute = o.GetAttribute<HtmlAttributeAttribute>()
                where attribute != null
                select new HtmlAttributeInfo(null, o, attribute);

            m_attributes = attributes.ToList();
        }

        public Type Type {
            get { return m_type; }
        }
        public IEnumerable<HtmlAttributeInfo> Attributes() {
            return m_attributes;
        }

        public override string ToString() {
            return m_type.FullName;
        }
    }

    [TextNodeContainsContent]
    public abstract class Html : TextNodes {

        private HtmlCommonAttributes m_attributes;

        public Html(HtmlCommonAttributes attributes = null) {
            m_attributes = attributes;
        }

        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            var elementInfo = HtmlLoader.GetElement(GetType());
            var elementAttributes = 
                from o in elementInfo.Attributes()
                let value = o.GetValue(this)
                where value != null
                select new TXAttribute<Tag>(o.Name, value);

            var commonAttributeInfo = HtmlLoader.GetAttributeGroup(typeof(HtmlCommonAttributes));
            var commonAttributes = m_attributes == null ? Enumerable.Empty<TXAttribute<Tag>>() :
                from o in commonAttributeInfo.Attributes()
                let value = o.GetValue(m_attributes)
                where value != null
                select new TXAttribute<Tag>(o.Name, value);

            var inline = elementInfo.Inline || 
                context.Ancestors<Html>().Any(o => 
                    HtmlLoader.GetElement(o.GetType()).Inline);

            return new TXElement<Tag>(elementInfo.Name,
                attributes: commonAttributes.Concat(elementAttributes),
                inline: inline) {

                Children()
            };
        }
    }

    [HtmlAttributeGroup]
    public sealed class HtmlCommonAttributes {

        [HtmlAttribute]
        public object Class { get; set; }

        [HtmlAttribute]
        public object Id { get; set; }
    }

    [HtmlElement("html")]
    public sealed class HtmlHtml : Html {
    }

    [HtmlElement("head")]
    public sealed class HtmlHead : Html {
    }

    [HtmlElement("link")]
    public sealed class HtmlLink : Html {

        private object m_rel, m_type, m_href;

        public HtmlLink(object rel = null, object type = null, object href = null) {
            m_rel = rel;
            m_type = type;
            m_href = href;
        }

        [HtmlAttribute]
        public object Rel {
            get { return m_rel; }
        }

        [HtmlAttribute]
        public object Type {
            get { return m_type; }
        }

        [HtmlAttribute]
        public object HRef {
            get { return m_href; }
        }
    }

    [HtmlElement("body")]
    public sealed class HtmlBody : Html {
    }

    [HtmlElement("pre", inline: true)]
    public sealed class HtmlPreformatted : Html {

        protected internal override IEnumerable<object> Children(TextNodeContext context) {
            return new TextIndentReset() { base.Children(context) }; 
        }
    }

    [HtmlElement("span", inline: true)]
    public sealed class HtmlSpan : Html {

        private object m_class;

        public HtmlSpan(object cls) {
            m_class = cls;
        }

        [HtmlAttribute]
        public object Class {
            get { return m_class; }
        }
    }

    [HtmlElement("div")]
    public sealed class HtmlDiv : Html {

        public HtmlDiv(HtmlCommonAttributes attributes = null)
            : base(attributes) {
        }
    }
    
    [HtmlElement("ul")]
    public sealed class HtmlUnorderedList : Html {

        public HtmlUnorderedList(HtmlCommonAttributes attributes = null)
            : base(attributes) {
        }
    }

    [HtmlElement("li", inline: true)]
    public sealed class HtmlListItem : Html {

        public HtmlListItem(HtmlCommonAttributes attributes = null)
            : base(attributes) {
        }
    }

    [HtmlElement("a", inline: true)]
    public sealed class HtmlAnchor : Html {

        private object m_href;

        public HtmlAnchor(object href) {
            m_href = href;
        }

        [HtmlAttribute]
        public object HRef { 
            get { return m_href; } 
        }
    }
}
